/*
 * This file is part of Cadmium.
 * Copyright (C) 2007-2010 Xavier Clerc.
 *
 * Cadmium is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Cadmium is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package fr.x9c.cadmium.kernel;

import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import fr.x9c.cadmium.primitives.systhreads.ThreadStatus;

/**
 * This abstract class is the parent class of all code runners generated by the
 * Cafesterol compiler. <br/>
 * It thus provides utility methods for native code runners.
 *
 * @author <a href="mailto:cadmium@x9c.fr">Xavier Clerc</a>
 * @version 1.2
 * @since 1.0
 */
public abstract class AbstractNativeRunner extends AbstractCodeRunner {

    /** Map from classes to constant values. */
    private final Map<Class, Value> constants;

    /** Map from identifiers to global values. */
    private final Map<String, Value> globals;

    /** Counter of initialized globals. */
    private int globalsInited;

    /** Registered closures. */
    private List<Method> closures;

    /** Result of code run. */
    protected Value result;

    /** Exception thrown by code run. */
    protected Throwable exception;

    /** Backtrace information. */
    private Throwable backtraceInfo;

    /**
     * Constructs a code runner from parameters.
     * @param np parameters - should not be <tt>null</tt>
     */
    public AbstractNativeRunner(final NativeParameters np) {
        super(new Context(np, true, new File(".")));
        this.constants = new HashMap<Class, Value>();
        this.globals = new HashMap<String, Value>();
        this.globalsInited = 0;
        this.closures = new ArrayList<Method>();
    } // end constructor(NativeParameters)

    /**
     * Copy constructor.
     * @param that instance to be copied - should not be <tt>null</tt>
     */
    public AbstractNativeRunner(final AbstractNativeRunner that) {
        super(that.context);
        this.constants = that.constants;
        this.globals = that.globals;
        this.globalsInited = that.globalsInited;
        this.closures = that.closures;
    } // end constructor(AbstractNativeRunner)

    /**
     * Returns the result of code execution.
     * @return the result of code execution
     */
    public final Value getResult() {
        return this.result;
    } // end method 'getResult()'

    /**
     * Process a pending signal if any.
     * @throws Fail.Exception as thrown by signal handler
     * @throws Fatal.Exception as thrown by signal handler
     * @throws CadmiumException as thrown by signal handler
     */
    public final void checkSignals()
        throws Fail.Exception, Fatal.Exception, FalseExit, CadmiumException {
        Signals.processSignal(this);
    } // end method 'checkSignals()'

    /**
     * Returns the atom of a given order.
     * @param atm order of the atom to return
     *            - should be in 0 .. {@link fr.x9c.cadmium.kernel.Context#NB_ATOMS} - 1
     * @return the atom of given order
     */
    public final Value getAtom(final int atm) {
        return this.context.getAtom(atm);
    } // end method 'getAtom(int)'

    /**
     * Loads the constants for a class.
     * @param id class to load constants for - should not be <tt>null</tt>
     * @throws CadmiumException if constants cannot be loaded
     */
    public final void loadConstant(final Class id)
        throws CadmiumException {
        try {
            final InputStream is =
                id.getResourceAsStream(id.getSimpleName() + ".consts");
            if (is == null) {
                throw new CadmiumException("unable to load constants for " + id);
            } // end if
            final DataInputStream dis = new DataInputStream(is);
            final Value consts = Intern.inputVal(this.context, dis, true);
            dis.close();
            is.close();
            this.constants.put(id, consts);
        } catch (final IOException ioe) {
            throw new CadmiumException("unable to load constants for " + id);
        } catch (final Fail.Exception fe) {
            throw new CadmiumException("unable to load constants for " + id);
        } catch (final Fatal.Exception fe) {
            throw new CadmiumException("unable to load constants for " + id);
        } // end try/catch
    } // end method 'loadConstant(Class)'

    /**
     * Returns a constant value.
     * @param id class to return constant value for - should not be <tt>null</tt>
     * @param idx constant index
     * @return constant of given class located at given index
     */
    public final Value getConstant(final Class id, final int idx) {
        assert id != null : "null id";
        return this.constants.get(id).asBlock().get(idx);
    } // end method 'getConstant(Class, int)'

    /**
     * Creates the global value for a class.
     * @param id global identifier - should not be <tt>null</tt>
     * @param sz size of global value - should be <tt>>= 0</tt>
     */
    public final void createGlobal(final String id, final int sz) {
        assert id != null : "null id";
        assert sz >= 0 : "sz should be >= 0";
        final Block bl = Block.createBlock(sz, 0);
        this.globals.put(id, Value.createFromBlock(bl));
    } // end method 'createGlobal(String, int)'

    /**
     * Registers a predefined exception with the runtime.
     * @param name exception name - should not be <tt>null</tt>
     */
    public final void registerPredefinedException(final String name) {
        assert name != null : "null name";
        final String bucketName = "caml_bucket_" + name;
        final String symName = "caml_exn_" + name;
        final Value symValue = Value.createFromBlock(Block.createBlock(0, Value.createFromBlock(Block.createString(name))));
        this.globals.put(symName, symValue);
        this.globals.put(bucketName, Value.createFromBlock(Block.createBlock(0, symValue)));
    } // end method 'registerPredefinedException(String)'

    /**
     * Returns the global value for a class.
     * @param id global identifier - should not be <tt>null</tt>
     * @return global value for the passed class
     */
    public final Value getGlobal(final String id) {
        assert id != null : "null id";
        return this.globals.get(id);
    } // end method 'getGlobal(String)'

    /**
     * Changes the global value for a class.
     * @param id global identifier - should not be <tt>null</tt>
     * @param val new global value
     */
    public final void setGlobal(final String id, final Value val) {
        assert id != null : "null id";
        this.globals.put(id, val);
    } // end method 'getGlobal(String)'

    /**
     * Returns the value of the counter of initialized globals.
     * @return the value of the counter of initialized globals
     */
    public final int getGlobalsInited() {
        return this.globalsInited;
    } // end method 'getGlobalsInited()'

    /**
     * Increments the counter of initialized globals.
     */
    public final void incrGlobalsInited() {
        this.globalsInited++;
    } // end method 'incrGlobalsInited()'

    /**
     * Tests if two values are equal.
     * @param v1 first value to compare - should not be <tt>null</tt>
     * @param v2 second value to compare - should not be <tt>null</tt>
     * @return <tt>true</tt> if the values are equal,
     *         <tt>false</tt> otherwise
     */
    public static boolean equalValues(final Value v1, final Value v2) {
        assert v1 != null : "null v1";
        assert v2 != null : "null v2";
        if (v1.isBlock()) {
            return v2.isBlock() ? v1.asBlock() == v2.asBlock() : false;
        } else {
            return v1.getRawValue() == v2.getRawValue();
        } // end if/else
    } // end method 'equalValues(Value, Value)'

    /**
     * Tests if two values are different.
     * @param v1 first value to compare - should not be <tt>null</tt>
     * @param v2 second value to compare - should not be <tt>null</tt>
     * @return <tt>true</tt> if the values are different,
     *         <tt>false</tt> otherwise
     */
    public static boolean notEqualValues(final Value v1, final Value v2) {
        assert v1 != null : "null v1";
        assert v2 != null : "null v2";
        return !equalValues(v1, v2);
    } // end method 'notEqualValues(Value, Value)'

    /**
     * Tests if a value is lower than another one.
     * @param v1 first value to compare - should not be <tt>null</tt>
     * @param v2 second value to compare - should not be <tt>null</tt>
     * @return <tt>true</tt> if the the first value is lower than the second one,
     *         <tt>false</tt> otherwise
     */
    public static boolean lowerThanValue(final Value v1, final Value v2) {
        assert v1 != null : "null v1";
        assert v2 != null : "null v2";
        return v1.universalLong() < v2.universalLong();
    } // end method 'lowerThanValue(Value, Value)'

    /**
     * Tests if a value is lower than or equal to another one.
     * @param v1 first value to compare - should not be <tt>null</tt>
     * @param v2 second value to compare - should not be <tt>null</tt>
     * @return <tt>true</tt> if the the first value is lower than or equal to
     *         the second one, <tt>false</tt> otherwise
     */
    public static boolean lowerEqualValue(final Value v1, final Value v2) {
        assert v1 != null : "null v1";
        assert v2 != null : "null v2";
        return v1.universalLong() <= v2.universalLong();
    } // end method 'lowerEqualValue(Value, Value)'

    /**
     * Tests if a value is greater than another one.
     * @param v1 first value to compare - should not be <tt>null</tt>
     * @param v2 second value to compare - should not be <tt>null</tt>
     * @return <tt>true</tt> if the the first value is greater than the second one,
     *         <tt>false</tt> otherwise
     */
    public static boolean greaterThanValue(final Value v1, final Value v2) {
        assert v1 != null : "null v1";
        assert v2 != null : "null v2";
        return v1.universalLong() > v2.universalLong();
    } // end method 'greaterThanValue(Value, Value)'

    /**
     * Tests if a value is greater than or equal to another one.
     * @param v1 first value to compare - should not be <tt>null</tt>
     * @param v2 second value to compare - should not be <tt>null</tt>
     * @return <tt>true</tt> if the the first value is greater than or equal
     *         to the second one, <tt>false</tt> otherwise
     */
    public static boolean greaterEqualValue(final Value v1, final Value v2) {
        assert v1 != null : "null v1";
        assert v2 != null : "null v2";
        return v1.universalLong() >= v2.universalLong();
    } // end method 'greaterEqualValue(Value, Value)'

    /**
     * Test if a value is in a given interval.
     * @param v1 value to test - should not be <tt>null</tt>
     * @param v2 value defining the interval - should not be <tt>null</tt>
     * @return <tt>true</tt> if <tt>v1</tt> is in <i>0..<tt>v2</tt></i>,
     *         <tt>false</tt> otherwise
     */
    public static boolean isOutValue(final Value v1, final Value v2) {
        assert v1 != null : "null v1";
        assert v2 != null : "null v2";
        return v1.universalUnsignedLong() < v2.universalUnsignedLong();
    } // end method 'isOutValue(Value, Value)'

    /**
     * Adds a closure to the list of closures.
     * @param meth closure to be added - should not be <tt>null</tt>
     * @return closure identifier
     */
    private int addClosure(final Method meth) {
        assert meth != null : "null meth";
        synchronized (this.closures) {
            final int res = this.closures.size();
            this.closures.add(meth);
            return res;
        } // end synchronized
    } // end method 'addClosure(Method)'

    /**
     * Returns a closure.
     * @param id closure identifier
     * @return the closure whose identifier is passed
     */
    private Method getClosure(final int id) {
        synchronized (this.closures) {
            return this.closures.get(id);
        } // end synchronized
    } // end method 'getClosure(int)'

    /**
     * Registers a closure from a function.
     * @param cls class declaring the function - should not be <tt>null</tt>
     * @param name function name - should not be <tt>null</tt>
     * @return a closure identifier value corresponding to the function
     * @throws Fatal.Exception if the function cannot be found
     */
    public final int registerClosure(final Class cls, final String name)
        throws Fatal.Exception {
        assert cls != null : "null cls";
        assert name != null : "null name";
        for (Method m : cls.getMethods()) {
            if (m.getName().equals(name)) {
                return addClosure(m);
            } // end if
        } // end for
        Fatal.raise("unknown function:" + name + " " + cls.getSimpleName());
        return 0; // never reached
    } // end method 'registerClosure(Class, String)'

    /**
     * Registers a closure from an unary function.
     * @param cls class declaring the function - should not be <tt>null</tt>
     * @param name function name - should not be <tt>null</tt>
     * @return a closure value corresponding to the function
     * @throws Fatal.Exception if the function cannot be found
     */
    public final Value registerClosure1(final Class cls, final String name)
        throws Fatal.Exception {
        assert cls != null : "null cls";
        assert name != null : "null name";
        for (Method m : cls.getMethods()) {
            if (m.getName().equals(name)) {
                final Block res = Block.createClosure(2);
                res.setCustom(res);
                res.setCode(addClosure(m));
                res.set(1, Value.ONE);
                return Value.createFromBlock(res);
            } // end if
        } // end for
        Fatal.raise("unknown function:" + name + " " + cls.getSimpleName());
        return null; // never reached
    } // end method 'registerClosure1(Class, String)'

    /**
     * Registers a closure from a n-ary function.
     * @param cls class declaring the function - should not be <tt>null</tt>
     * @param name function name - should not be <tt>null</tt>
     * @param arity function arity - should be <tt>&gt; 1</tt>
     * @return a closure value corresponding to the function
     * @throws Fatal.Exception if the function cannot be found
     */
    public final Value registerClosureN(final Class cls,
                                        final String name,
                                        final int arity)
        throws Fatal.Exception {
        assert cls != null : "null cls";
        assert name != null : "null name";
        assert arity > 1 : "arity should be > 1";
        for (Method m : cls.getMethods()) {
            if (m.getName().equals(name)) {
                final int code = arity < 0
                    ? -1
                    : -arity - 1;
                final Block res = Block.createClosure(3);
                res.setCustom(res);
                res.setCode(code);
                res.set(1, Value.createFromLong(arity));
                res.set(2, Value.createFromLong(addClosure(m)));
                return Value.createFromBlock(res);
            } // end if
        } // end for
        Fatal.raise("unknown function:" + name + " " + cls.getSimpleName());
        return null; // never reached
    } // end method 'registerClosureN(Class, String, int)'

    /**
     * Registers an infix block from an unary function.
     * @param parent parent block - should not be <tt>null</tt>
     * @param idx where to store the infix block in the parent block
     * @param cls class declaring the function - should not be <tt>null</tt>
     * @param name function name - should not be <tt>null</tt>
     * @throws Fatal.Exception if the function cannot be found
     */
    public final void registerInfix1(final Block parent,
                                     final int idx,
                                     final Class cls,
                                     final String name)
        throws Fatal.Exception {
        assert parent != null : "null parent";
        assert cls != null : "null cls";
        assert name != null : "null name";
        for (Method m : cls.getMethods()) {
            if (m.getName().equals(name)) {
                final Block infix = Block.createInfix(idx + 1);
                infix.setParent(parent);
                infix.setCustom(infix);
                infix.setCode(addClosure(m));
                parent.set(idx, Value.createFromRawValue(infix.getTag()));
                parent.set(idx + 1, Value.createFromBlock(infix));
                parent.set(idx + 2, Value.ONE);
                return;
            } // end if
        } // end for
        Fatal.raise("unknown function:" + name + " " + cls.getSimpleName());
    } // end method 'registerInfix1(Block, int, Class, String)'

    /**
     * Registers an infix block from a n-ary function.
     * @param parent parent block - should not be <tt>null</tt>
     * @param idx where to store the infix block in the parent block
     * @param cls class declaring the function - should not be <tt>null</tt>
     * @param name function name - should not be <tt>null</tt>
     * @param arity function arity - should be <tt>&gt; 1</tt>
     * @throws Fatal.Exception if the function cannot be found
     */
    public final void registerInfixN(final Block parent,
                                     final int idx,
                                     final Class cls,
                                     final String name,
                                     final int arity)
        throws Fatal.Exception {
        assert parent != null : "null parent";
        assert cls != null : "null cls";
        assert name != null : "null name";
        assert arity > 1 : "arity should be > 1";
        for (Method m : cls.getMethods()) {
            if (m.getName().equals(name)) {
                final int code = arity < 0
                    ? -1
                    : -arity - 1;
                final Block infix = Block.createInfix(idx + 1);
                infix.setParent(parent);
                infix.setCustom(infix);
                infix.setCode(code);
                parent.set(idx, Value.createFromRawValue(infix.getTag()));
                parent.set(idx + 1, Value.createFromBlock(infix));
                parent.set(idx + 2, Value.createFromLong(arity));
                parent.set(idx + 3, Value.createFromLong(addClosure(m)));
                return;
            } // end if
        } // end for
        Fatal.raise("unknown function:" + name + " " + cls.getSimpleName());
    } // end method 'registerInfixN(Block, int, Class, String, int)'

    /**
     * {@inheritDoc}
     */
    @Override
    public final Value callback(final Value closure, final Value... params)
        throws Fail.Exception, Fatal.Exception, FalseExit {
        assert closure != null : "null closure";
        assert params != null : "null params";
        assert params.length + 4 <= 256 : "params is too long";
        final Thread currentThread = Thread.currentThread();
        final boolean isCadmiumThread = currentThread instanceof CadmiumThread;
        if (!isCadmiumThread) {
            this.context.addAdditionalThread(currentThread);
        } // end if
        final AbstractNativeRunner runner = copy();
        runner.threadStatus = null;
        runner.setup(closure, params);
        try {
            runner.run();
        } finally {
            if (!isCadmiumThread) {
                this.context.removeAdditionalThread(currentThread);
            } // end if
        } // end try/finally
        if (runner.exception != null) {
            if (runner.exception instanceof Fail.Exception) {
                throw (Fail.Exception) runner.exception;
            } else if (runner.exception instanceof Fatal.Exception) {
                throw (Fatal.Exception) runner.exception;
            } else if (runner.exception instanceof FalseExit) {
                final boolean backtrace = this.context.isBacktraceActive();
                this.context.setBacktraceActive(false);
                final Value atExit =
                    this.context.getCallback("Pervasives.do_at_exit");
                if (atExit != null) {
                    try {
                        callback(atExit, Value.UNIT);
                    } catch (final Throwable t) {
                        // error in such callbacks are silently ignored
                    } // end try/catch
                } // end if
                this.context.setBacktraceActive(backtrace);
                throw (FalseExit) runner.exception;
            } else {
                Fatal.raise("error in callback: " + runner.exception);
            } // end if/elsif/else
        } // end if
        return runner.result;
    } // end method 'callback(Value, Value...)'

    /**
     * Runs the given closure (main program if no closure has been provided)
     * with the given arguments, setting result and exception.
     */
    public final void run() {
        this.result = null;
        this.exception = null;
        try {
            context.leaveBlockingSection();
        } catch (final Fail.Exception fe) {
            return;
        } catch (final FalseExit fe) {
            return;
        } // end try/catch
        try {
            if (this.closure == null) {
                try {
                    moduleMain();
                } catch (final Throwable t) {
                    setBacktraceInfo(t);
                    this.exception = t;
                } // end try/catch
            } else {
                final Application app = new Application(this,
                                                        this.closure,
                                                        this.args.length);
                for (int i = this.args.length - 1; i >= 0; i--) {
                    app.params[app.next--] = this.args[i];
                } // end for
                try {
                    this.result = returnApplication(app);
                } catch (final Throwable t) {
                    setBacktraceInfo(t);
                    this.exception = t;
                } // end try/catch
            } // end if/else
        } finally {
            if (this.threadStatus != null) {
                ((ThreadStatus) this.threadStatus.asBlock().get(TERMINATED).asBlock().asCustom()).terminate();
            } // end if
            context.enterBlockingSection();
        } // end try/finally
    } // end method 'run()'

    /**
     * <i>Main</i> method of the native runner.
     */
    public final void execute() {
        this.context.setMainCodeRunner(this);
        setup(null);
        final CadmiumThread thread =
            new CadmiumThread(this.context.getThreadGroup(), this);
        this.context.setMainThread(thread);
        thread.start();
        while (thread.isAlive()) {
            try {
                thread.join();
            } catch (final InterruptedException ie) {
                return;
            } // end try/catch
        } // end while
        Signals.unregisterContext(this.context);
        this.context.clearSignals();
        if ((this.exception != null)
            && !(this.exception instanceof FalseExit)) {
            final Channel ch = this.context.getChannel(Channel.STDERR);
            final PrintStream err;
            final ByteArrayOutputStream altErr;
            if ((ch != null) && (ch.asOutputStream() != null)) {
                altErr = null;
                err = new PrintStream(ch.asOutputStream(), true);
            } else {
                altErr = new ByteArrayOutputStream();
                err = new PrintStream(altErr, true);
            } // end if/else
            final boolean backtrace = this.context.isBacktraceActive();
            this.context.setBacktraceActive(false);
            final Value atExit =
                this.context.getCallback("Pervasives.do_at_exit");
            if (atExit != null) {
                try {
                    callback(atExit, Value.UNIT);
                } catch (final Throwable t) {
                    // error in such callbacks are silently ignored
                } // end try/catch
            } // end if
            this.context.setBacktraceActive(backtrace);
            if (this.exception instanceof Fail.Exception) {
                final String msg =
                    Misc.convertException(((Fail.Exception) this.exception).asValue(this));
                err.println("Fatal error: exception " + msg);
                if (this.context.isBacktraceActive()) {
                    printExceptionBacktrace(err);
                } // end if
                err.close();
                return;
            } else if (this.exception instanceof Fatal.Exception) {
                err.println(((Fatal.Exception) this.exception).getMessage());
                err.close();
                return;
            } else {
                err.println(this.exception.toString());
                err.close();
                return;
            } // end if/elsif/else
        } else {
            final boolean backtrace = this.context.isBacktraceActive();
            this.context.setBacktraceActive(false);
            final Value atExit =
                this.context.getCallback("Pervasives.do_at_exit");
            if (atExit != null) {
                try {
                    callback(atExit, Value.UNIT);
                } catch (final Throwable t) {
                    // error in such callbacks are silently ignored
                } // end try/catch
            } // end if
            this.context.setBacktraceActive(backtrace);
        } // end if/else
    } // end method 'execute()'

    /**
     * <i>Main</i> method of the native runner.
     * @param bindings script bindings - should not be <tt>null</tt>
     */
    public final void executeWithBindings(final Map<String, Value> bindings) {
        this.bindings = bindings;
        execute();
    } // end method 'executeWithBindings(Map<String, Value>)'

    /**
     * Filters the passed stack trace according to whether simplified back trace
     * is enabled.
     * @param elems stack trace to filter - should not be <tt>null</tt>
     * @return the filtered stack trace
     */
    private StackTraceElement[] filterBackTrace(final StackTraceElement[] elems) {
        assert elems != null : "null elems";
        final boolean simplified =
            ((NativeParameters) this.context.getParameters()).isSimplifiedBacktrace();
        final List<StackTraceElement> l = new ArrayList<StackTraceElement>(Arrays.asList(elems));
        final Iterator<StackTraceElement> it = l.iterator();
        while (it.hasNext()) {
            final StackTraceElement e = it.next();
            final String className = e.getClassName();
            final boolean filtered =
                className.startsWith("java.lang.")
                || className.startsWith("java.lang.reflect.")
                || className.startsWith("fr.x9c.cadmium.kernel.");
            if ((simplified && filtered)
                || (e.getFileName() == null)
                || (e.getLineNumber() < 0)) {
                it.remove();
            } // end if
        } // end while
        return l.toArray(new StackTraceElement[l.size()]);
    } // end method 'filterBackTrace(StackTraceElement[])'

    /**
     * {@inheritDoc}
     */
    @Override
    public final void printExceptionBacktrace(final PrintStream out) {
        assert out != null : "null out";
        for (StackTraceElement elem : filterBackTrace(this.exception.getStackTrace())) {
            out.println("\tat " + elem);
        } // end for
    } // end method 'printExceptionBacktrace(PrintStream)'

    /**
     * Sets the backtrace information.
     * @param t new backtrace information
     */
    public void setBacktraceInfo(final Throwable t) {
        if (this.context.isBacktraceActive()) {
            this.backtraceInfo = t;
        } // end if
    } // end method 'setBacktraceInfo(Throwable)'

    /**
     * {@inheritDoc}
     */
    @Override
    public void clearBacktraceInfo() {
        this.backtraceInfo = null;
    } // end method 'clearBacktraceInfo()'

    /**
     * {@inheritDoc}
     */
    @Override
    public Value getExceptionBacktrace() {
        if (this.backtraceInfo != null) {
            final StackTraceElement[] l = filterBackTrace(this.backtraceInfo.getStackTrace());
            final int len = l.length;
            final Block arr = Block.createBlock(len, 0);
            for (int i = 0; i < len; i++) {
                final StackTraceElement e = l[i];
                final Block p = Block.createBlock(5, 0);
                p.set(0, i == 0 ? Value.TRUE : Value.FALSE);
                p.set(1, Value.createFromBlock(Block.createString(e.getFileName())));
                p.set(2, Value.createFromLong(e.getLineNumber()));
                p.set(3, Value.ZERO);
                p.set(4, Value.ZERO);
                arr.set(i, Value.createFromBlock(p));
            } // end for
            final Block res = Block.createBlock(0, Value.createFromBlock(arr));
            return Value.createFromBlock(res);
        } else {
            final Block arr = Block.createBlock(0, 0);
            final Block res = Block.createBlock(0, Value.createFromBlock(arr));
            return Value.createFromBlock(res);
        } // end if/else
    } // end method 'getExceptionBacktrace()'

    /**
     * Create a new thread, as a code runner.
     * @param status initial thread status - can be <tt>null</tt>
     * @return a new thread
     */
    public final CodeRunner createNewThread(final Value status) {
        final AbstractCodeRunner res = copy();
        if (status != null) {
            res.setThreadStatus(status);
        } // end if
        return res;
    } // end method 'createNewThread(Value)'

    /**
     * Constructs a copy of the current instance.
     */
    protected abstract AbstractNativeRunner copy();

    /**
     * Main function of the native compiled program.
     */
    protected abstract void moduleMain();

    /**
     * Creates a block of size 1.
     * @param p1 value in the block
     * @return a new block with passed tag and value
     */
    public static Value createBlock(final Value p1,
                                    final int tag) {
        return Value.createFromBlock(Block.createBlock(tag, p1));
    } // end method 'createBlock(Value, int)'

    /**
     * Creates a block of size 2.
     * @param p1 first value in the block
     * @param p2 second value in the block
     * @return a new block with passed tag and values
     */
    public static Value createBlock(final Value p1,
                                    final Value p2,
                                    final int tag) {
        return Value.createFromBlock(Block.createBlock(tag, p1, p2));
    } // end method 'createBlock(Value, Value, int)'

    /**
     * Creates a block of size 3.
     * @param p1 first value in the block
     * @param p2 second value in the block
     * @param p3 third value in the block
     * @return a new block with passed tag and values
     */
    public static Value createBlock(final Value p1,
                                    final Value p2,
                                    final Value p3,
                                    final int tag) {
        return Value.createFromBlock(Block.createBlock(tag, p1, p2, p3));
    } // end method 'createBlock(Value, Value, Value, int)'

    /**
     * Creates a block of size 4.
     * @param p1 first value in the block
     * @param p2 second value in the block
     * @param p3 third value in the block
     * @param p4 fourth value in the block
     * @return a new block with passed tag and values
     */
    public static Value createBlock(final Value p1,
                                    final Value p2,
                                    final Value p3,
                                    final Value p4,
                                    final int tag) {
        return Value.createFromBlock(Block.createBlock(tag, p1, p2, p3, p4));
    } // end method 'createBlock(Value, Value, Value, Value, int)'

    /**
     * Creates a <i>Values</i> to be folded.
     * @param tag value tag
     * @param n number of values to fold
     */
    public static Values createValues(final int tag, final int n) {
        return new Values(tag, n);
    } // end method 'createValues(int, int)'

    /**
     * Creates a <i>Values</i> to be folded.
     * @param n number of values to fold
     */
    public static Values createDoubles(final int n) {
        return new Values(0, n);
    } // end method 'createDoubles(int)'

    /**
     * Fold one value into a <i>Values</i>.
     * @param p1 value to fold - should not be <tt>null</tt>
     * @param v values to be folded - should not be <tt>null</tt>
     * @return <tt>v</tt>
     */
    public static Values foldValues1(final Value p1,
                                     final Values v) {
        assert p1 != null : "null p1";
        assert v != null : "null v";
        v.elements[v.next--] = p1;
        return v;
    } // end method 'foldValues1(Value, Values)'

    /**
     * Fold two values into a <i>Values</i>.
     * @param p1 value to fold - should not be <tt>null</tt>
     * @param p2 value to fold - should not be <tt>null</tt>
     * @param v values to be folded - should not be <tt>null</tt>
     * @return <tt>v</tt>
     */
    public static Values foldValues2(final Value p1,
                                     final Value p2,
                                     final Values v) {
        assert p1 != null : "null p1";
        assert p2 != null : "null p2";
        assert v != null : "null v";
        v.elements[v.next--] = p2;
        v.elements[v.next--] = p1;
        return v;
    } // end method 'foldValues2(Value, Value, Values)'

    /**
     * Fold three values into a <i>Values</i>.
     * @param p1 value to fold - should not be <tt>null</tt>
     * @param p2 value to fold - should not be <tt>null</tt>
     * @param p3 value to fold - should not be <tt>null</tt>
     * @param v values to be folded - should not be <tt>null</tt>
     * @return <tt>v</tt>
     */
    public static Values foldValues3(final Value p1,
                                     final Value p2,
                                     final Value p3,
                                     final Values v) {
        assert p1 != null : "null p1";
        assert p2 != null : "null p2";
        assert p3 != null : "null p3";
        assert v != null : "null v";
        v.elements[v.next--] = p3;
        v.elements[v.next--] = p2;
        v.elements[v.next--] = p1;
        return v;
    } // end method 'foldValues3(Value, Value, Value, Values)'

    /**
     * Fold four values into a <i>Values</i>.
     * @param p1 value to fold - should not be <tt>null</tt>
     * @param p2 value to fold - should not be <tt>null</tt>
     * @param p3 value to fold - should not be <tt>null</tt>
     * @param p4 value to fold - should not be <tt>null</tt>
     * @param v values to be folded - should not be <tt>null</tt>
     * @return <tt>v</tt>
     */
    public static Values foldValues4(final Value p1,
                                     final Value p2,
                                     final Value p3,
                                     final Value p4,
                                     final Values v) {
        assert p1 != null : "null p1";
        assert p2 != null : "null p2";
        assert p3 != null : "null p3";
        assert p4 != null : "null p4";
        assert v != null : "null v";
        v.elements[v.next--] = p4;
        v.elements[v.next--] = p3;
        v.elements[v.next--] = p2;
        v.elements[v.next--] = p1;
        return v;
    } // end method 'foldValues4(Value, Value, Value, Value, Values)'

    /**
     * Converts a folded <i>Values</i> into a classical value.
     * @param v <i>Values</i> to convert - should not be <tt>null</tt>
     * @return corresponding value
     */
    public static Value returnValues(final Values v) {
        assert v != null : "null v";
        final Block res = Block.createBlock(v.n, v.tag);
        for (int i = 0; i < v.n; i++) {
            res.set(i, v.elements[i]);
        } // end for
        return Value.createFromBlock(res);
    } // end method 'returnValues(Values)'

    /**
     * Converts a folded <i>Values</i> into a value (double block).
     * @param d <i>Values</i> to convert - should not be <tt>null</tt>
     * @return corresponding value as a double block
     */
    public static Value returnDoubles(final Values d) {
        assert d != null : "null d";
        final Block res = Block.createDoubleArray(d.n);
        for (int i = 0; i < d.n; i++) {
            res.setDouble(i, d.elements[i].asBlock().asDouble());
        } // end for
        return Value.createFromBlock(res);
    } // end method 'returnDoubles(Values)'

    /**
     * Creates a <i>Closure</i> to be folded.
     * @param index closure index
     * @param n number of values to fold
     */
    public static Closure createClosureVars(final int index,
                                            final int n) {
        return new Closure(index, n);
    } // end method 'createClosureVars(int, int)'

    /**
     * Fold one value into a <i>Closure</i>.
     * @param p1 value to fold - should not be <tt>null</tt>
     * @param c closure to be folded - should not be <tt>null</tt>
     * @return <tt>c</tt>
     */
    public static Closure foldClosure1(final Value p1,
                                       final Closure c) {
        assert p1 != null : "null p1";
        assert c != null : "null c";
        c.elements[c.next--] = p1;
        return c;
    } // end method 'foldClosure1(Value, Closure)'

    /**
     * Fold two values into a <i>Closure</i>.
     * @param p1 value to fold - should not be <tt>null</tt>
     * @param p2 value to fold - should not be <tt>null</tt>
     * @param c closure to be folded - should not be <tt>null</tt>
     * @return <tt>c</tt>
     */
    public static Closure foldClosure2(final Value p1,
                                       final Value p2,
                                       final Closure c) {
        assert p1 != null : "null p1";
        assert p2 != null : "null p2";
        assert c != null : "null c";
        c.elements[c.next--] = p2;
        c.elements[c.next--] = p1;
        return c;
    } // end method 'foldClosure2(Value, Value, Closure)'

    /**
     * Fold three values into a <i>Closure</i>.
     * @param p1 value to fold - should not be <tt>null</tt>
     * @param p2 value to fold - should not be <tt>null</tt>
     * @param p3 value to fold - should not be <tt>null</tt>
     * @param c closure to be folded - should not be <tt>null</tt>
     * @return <tt>c</tt>
     */
    public static Closure foldClosure3(final Value p1,
                                       final Value p2,
                                       final Value p3,
                                       final Closure c) {
        assert p1 != null : "null p1";
        assert p2 != null : "null p2";
        assert p3 != null : "null p3";
        assert c != null : "null c";
        c.elements[c.next--] = p3;
        c.elements[c.next--] = p2;
        c.elements[c.next--] = p1;
        return c;
    } // end method 'foldClosure3(Value, Value, Value, Closure)'

    /**
     * Fold four values into a <i>Closure</i>.
     * @param p1 value to fold - should not be <tt>null</tt>
     * @param p2 value to fold - should not be <tt>null</tt>
     * @param p3 value to fold - should not be <tt>null</tt>
     * @param p4 value to fold - should not be <tt>null</tt>
     * @param c closure to be folded - should not be <tt>null</tt>
     * @return <tt>c</tt>
     */
    public static Closure foldClosure4(final Value p1,
                                       final Value p2,
                                       final Value p3,
                                       final Value p4,
                                       final Closure c) {
        assert p1 != null : "null p1";
        assert p2 != null : "null p2";
        assert p3 != null : "null p3";
        assert p4 != null : "null p4";
        assert c != null : "null c";
        c.elements[c.next--] = p4;
        c.elements[c.next--] = p3;
        c.elements[c.next--] = p2;
        c.elements[c.next--] = p1;
        return c;
    } // end method 'foldClosure4(Value, Value, Value, Value, Closure)'

    /**
     * Uses a folded <i>Closure</i> to populate a closure.
     * @param closure closure to populate - should not be <tt>null</tt>
     * @param c <i>Closure</i> containing values - should not be <tt>null</tt>
     * @return closure as a value
     */
    public static Value returnClosure(final Block closure, final Closure c) {
        assert closure != null : "null closure";
        assert c != null : "null c";
        final Block res = closure;
        for (int i = 0; i < c.n; i++) {
            res.set(c.index + i, c.elements[i]);
        } // end for
        return Value.createFromBlock(res);
    } // end method 'returnClosure(Block, Closure)'

    /**
     * Function application (whether full or partial).
     * @param p1 parameter to function - should not be <tt>null</tt>
     * @param closure closure to apply parameter to
     *                - should not be <tt>null</tt>
     * @return result of function application
     * @throws Fatal.Exception if function application fails
     */
    public final Value apply1(final Value p1,
                              final Value closure)
        throws Fail.Exception, Fatal.Exception, FalseExit {
        assert p1 != null : "null p1";
        assert closure != null : "null closure";
        final Application app = new Application(this, closure, 1);
        app.params[app.next--] = p1;
        return returnApplication(app);
    } // end method 'apply1(Value, Value)'

    /**
     * Function application (whether full or partial).
     * @param p1 parameter to function - should not be <tt>null</tt>
     * @param p2 parameter to function - should not be <tt>null</tt>
     * @param closure closure to apply parameters to
     *                - should not be <tt>null</tt>
     * @return result of function application
     * @throws Fatal.Exception if function application fails
     */
    public final Value apply2(final Value p1,
                              final Value p2,
                              final Value closure)
        throws Fail.Exception, Fatal.Exception, FalseExit {
        assert p1 != null : "null p1";
        assert p2 != null : "null p2";
        assert closure != null : "null closure";
        final Application app = new Application(this, closure, 2);
        app.params[app.next--] = p2;
        app.params[app.next--] = p1;
        return returnApplication(app);
    } // end method 'apply2(Value, Value, Value)'

    /**
     * Function application (whether full or partial).
     * @param p1 parameter to function - should not be <tt>null</tt>
     * @param p2 parameter to function - should not be <tt>null</tt>
     * @param p3 parameter to function - should not be <tt>null</tt>
     * @param closure closure to apply parameters to
     *                - should not be <tt>null</tt>
     * @return result of function application
     * @throws Fatal.Exception if function application fails
     */
    public final Value apply3(final Value p1,
                              final Value p2,
                              final Value p3,
                              final Value closure)
        throws Fail.Exception, Fatal.Exception, FalseExit {
        assert p1 != null : "null p1";
        assert p2 != null : "null p2";
        assert p3 != null : "null p3";
        assert closure != null : "null closure";
        final Application app = new Application(this, closure, 3);
        app.params[app.next--] = p3;
        app.params[app.next--] = p2;
        app.params[app.next--] = p1;
        return returnApplication(app);
    } // end method 'apply3(Value, Value, Value, Value)'

    /**
     * Function application (whether full or partial).
     * @param p1 parameter to function - should not be <tt>null</tt>
     * @param p2 parameter to function - should not be <tt>null</tt>
     * @param p3 parameter to function - should not be <tt>null</tt>
     * @param p4 parameter to function - should not be <tt>null</tt>
     * @param closure closure to apply parameters to
     *                - should not be <tt>null</tt>
     * @return result of function application
     * @throws Fatal.Exception if function application fails
     */
    public final Value apply4(final Value p1,
                              final Value p2,
                              final Value p3,
                              final Value p4,
                              final Value closure)
        throws Fail.Exception, Fatal.Exception, FalseExit {
        assert p1 != null : "null p1";
        assert p2 != null : "null p2";
        assert p3 != null : "null p3";
        assert p4 != null : "null p4";
        assert closure != null : "null closure";
        final Application app = new Application(this, closure, 4);
        app.params[app.next--] = p4;
        app.params[app.next--] = p3;
        app.params[app.next--] = p2;
        app.params[app.next--] = p1;
        return returnApplication(app);
    } // end method 'apply4(Value, Value, Value, Value, Value)'

    /**
     * Creates an <i>Application</i> to be folded.
     * @param closure application closure - should not be <tt>null</tt>
     * @param n number of values to fold
     */
    public final Application createApplication(final Value closure,
                                               final int n) {
        assert closure != null : "null closure";
        return new Application(this, closure, n);
    } // end method 'createApplication(Value, int)'

    /**
     * Fold one value into an <i>Application</i>.
     * @param p1 value to fold - should not be <tt>null</tt>
     * @param app application to be folded - should not be <tt>null</tt>
     * @return <tt>app</tt>
     */
    public static Application foldApplication1(final Value p1,
                                               final Application app) {
        assert p1 != null : "null p1";
        assert app != null : "null app";
        app.params[app.next--] = p1;
        return app;
    } // end method 'foldApplication1(Value, Application)'

    /**
     * Fold two values into an <i>Application</i>.
     * @param p1 value to fold - should not be <tt>null</tt>
     * @param p2 value to fold - should not be <tt>null</tt>
     * @param app application to be folded - should not be <tt>null</tt>
     * @return <tt>app</tt>
     */
    public static Application foldApplication2(final Value p1,
                                               final Value p2,
                                               final Application app) {
        assert p1 != null : "null p1";
        assert p2 != null : "null p2";
        assert app != null : "null app";
        app.params[app.next--] = p2;
        app.params[app.next--] = p1;
        return app;
    } // end method 'foldApplication2(Value, Value, Application)'

    /**
     * Fold three values into an <i>Application</i>.
     * @param p1 value to fold - should not be <tt>null</tt>
     * @param p2 value to fold - should not be <tt>null</tt>
     * @param p3 value to fold - should not be <tt>null</tt>
     * @param app application to be folded - should not be <tt>null</tt>
     * @return <tt>app</tt>
     */
    public static Application foldApplication3(final Value p1,
                                               final Value p2,
                                               final Value p3,
                                               final Application app) {
        assert p1 != null : "null p1";
        assert p2 != null : "null p2";
        assert p3 != null : "null p3";
        assert app != null : "null app";
        app.params[app.next--] = p3;
        app.params[app.next--] = p2;
        app.params[app.next--] = p1;
        return app;
    } // end method 'foldApplication3(Value, Value, Value, Application)'

    /**
     * Fold four values into an <i>Application</i>.
     * @param p1 value to fold - should not be <tt>null</tt>
     * @param p2 value to fold - should not be <tt>null</tt>
     * @param p3 value to fold - should not be <tt>null</tt>
     * @param p4 value to fold - should not be <tt>null</tt>
     * @param app application to be folded - should not be <tt>null</tt>
     * @return <tt>app</tt>
     */
    public static Application foldApplication4(final Value p1,
                                               final Value p2,
                                               final Value p3,
                                               final Value p4,
                                               final Application app) {
        assert p1 != null : "null p1";
        assert p2 != null : "null p2";
        assert p3 != null : "null p3";
        assert p4 != null : "null p4";
        assert app != null : "null app";
        app.params[app.next--] = p4;
        app.params[app.next--] = p3;
        app.params[app.next--] = p2;
        app.params[app.next--] = p1;
        return app;
    } // end method 'foldApplication4(Value, Value, Value, Value, Application)'

    /**
     * Evaluates an application.
     * @param app application to evaluate - should not be <tt>null</tt>
     * @return result of application (can be either a bare value or a closure)
     * @throws Fail.Exception as thrown by the executed closure
     * @throws Fatal.Exception if an error occurs during application
     */
    public static Value returnApplication(final Application app)
        throws Fail.Exception, Fatal.Exception, FalseExit, Error {
        assert app != null : "null app";
        final AbstractNativeRunner that = app.that;
        try {
            final int code = app.closure.asBlock().getCode();
            if (code >= 0) { // total application (arity is 1)
                final Block env = (Block) app.closure.asBlock().asCustom();
                final Method meth = that.getClosure(code);
                final Object[] objs;
                if (meth.getParameterTypes().length == 2) {
                    objs = new Object[] { that, app.params[0] };
                } else {
                    objs = new Object[] { that,
                                          app.params[0],
                                          Value.createFromBlock(env) };
                } // end if/else
                final Value tmp = (Value) meth.invoke(null, objs);
                if (app.n == 1) {
                    return tmp;
                } else {
                    final Application newApp = new Application(app.that,
                                                               tmp,
                                                               app.n - 1);
                    for (int i = 1; i < app.n; i++) {
                        newApp.params[i - 1] = app.params[i];
                    } // end for
                    return returnApplication(newApp);
                } // end if/else
            } else if (code == -1) { // total application (tuplified)
                final Block env = (Block) app.closure.asBlock().asCustom();
                final Method meth =
                    that.getClosure(app.closure.asBlock().get(2).asLong());
                final int arity = meth.getParameterTypes().length - 1;
                final Block bl = app.params[0].asBlock();
                final Object[] objs = new Object[arity + 1];
                objs[0] = that;
                objs[arity] = env == null
                    ? null
                    : Value.createFromBlock(env);
                final int len = bl.sizeValues();
                for (int i = 0; i < len; i++) {
                    objs[i + 1] = bl.get(i);
                } // end for
                final Value tmp = (Value) meth.invoke(null, objs);
                if (app.n == 1) {
                    return tmp;
                } else {
                    final Application newApp = new Application(app.that,
                                                               tmp,
                                                               app.n - 1);
                    for (int i = 1; i < app.n; i++) {
                        newApp.params[i - 1] = app.params[i];
                    } // end for
                    return returnApplication(newApp);
                } // end if/else
            } else { // curried application
                final Block env = (Block) app.closure.asBlock().asCustom();
                final Method meth =
                    that.getClosure(app.closure.asBlock().get(2).asLong());
                final int funArity = meth.getParameterTypes().length - 1;
                final int remArity = -(code + 1);
                if (remArity - app.n <= 0) { // total application
                    final Object[] objs = new Object[funArity + 1];
                    objs[0] = that;
                    objs[funArity] = env == null
                        ? null
                        : Value.createFromBlock(env);
                    if (env != app.closure.asBlock()) {
                        final int len = app.closure.asBlock().sizeValues();
                        for (int i = 3; i < len; i++) {
                            objs[i - 2] = app.closure.asBlock().get(i);
                        } // end for
                        for (int i = 0; i < Math.min(app.n, remArity); i++) {
                            objs[i + len - 2] = app.params[i];
                        } // end for
                    } else {
                        for (int i = 0; i < Math.min(app.n, remArity); i++) {
                            objs[i + 1] = app.params[i];
                        } // end for
                    } // end if/else
                    final Value tmp = (Value) meth.invoke(null, objs);
                    if (remArity - app.n == 0) {
                        return tmp;
                    } else {
                        final int newN = app.n - remArity;
                        final Application newApp = new Application(app.that,
                                                                   tmp,
                                                                   newN);
                        for (int i = 0; i < newN; i++) {
                            newApp.params[i] = app.params[remArity + i];
                        } // end for
                        return returnApplication(newApp);
                    } // end if/else
                } else { // partial application
                    final int newArity = remArity - app.n;
                    final int blockSize = env != app.closure.asBlock()
                        ? app.closure.asBlock().sizeValues() + app.n
                        : 3 + app.n;
                    final Block newBlock = Block.createClosure(blockSize);
                    final int newCode = -newArity - 1;
                    newBlock.setCustom(env);
                    newBlock.setCode(newCode);
                    newBlock.set(1, Value.createFromLong(newArity));
                    newBlock.set(2, app.closure.asBlock().get(2));
                    if (env != app.closure.asBlock()) {
                        final int len = app.closure.asBlock().sizeValues();
                        for (int i = 3; i < len; i++) {
                            newBlock.set(i, app.closure.asBlock().get(i));
                        } // end for
                        for (int i = 0; i < app.n; i++) {
                            newBlock.set(i + len, app.params[i]);
                        } // end for
                    } else {
                        for (int i = 0; i < app.n; i++) {
                            newBlock.set(3 + i, app.params[i]);
                        } // end for
                    } // end if/else
                    return Value.createFromBlock(newBlock);
                } // end if/else
            } // end if/elsif/else
        } catch (final java.lang.reflect.InvocationTargetException ite) {
            final Throwable te = ite.getTargetException();
            if (te instanceof Fail.Exception) {
                throw (Fail.Exception) te;
            } else if (te instanceof Fatal.Exception) {
                throw (Fatal.Exception) te;
            } else if (te instanceof FalseExit) {
                throw (FalseExit) te;
            } else {
                Fatal.raise("error in apply: " + te.toString());
                return null; // never reached
            } // end if/elsif/else
        } catch (final IllegalAccessException iae) {
            Fatal.raise("error in apply: illegal access exception");
            return null; // never reached
        } // end try/catch
    } // end method 'returnApplication(Application)'

    /**
     * An application is a mere data structure containing the data of
     * a function application.
     */
    private static final class Application {

        /** Code runner for application. */
        private AbstractNativeRunner that;

        /** Number of values. */
        private final int n;

        /** Index of next value to be set (going backwards). */
        private int next;

        /** Application parameters. */
        private final Value[] params;

        /** Application closure. */
        private final Value closure;

        /**
         * Constructs an application from code runner and closure.
         * @param that code runner used to execute closure - should not be <tt>null</tt>
         * @param closure application closure - should not be <tt>null</tt>
         * @param n number of parameters to closure - should be <tt>&gt; 0</tt>
         */
        private Application(final AbstractNativeRunner that,
                            final Value closure,
                            final int n) {
            assert that != null : "null that";
            assert closure != null : "null closure";
            assert n > 0 : "n should be > 0";
            this.that = that;
            this.n = n;
            this.next = n - 1;
            this.params = new Value[n];
            this.closure = closure;
        } // end constructor(AbstractNativeRunner, Value, int)

    } // end inner-class 'Application'

    /**
     * A <i>Values</i> is a mere data structure containg a list of values.
     */
    private static final class Values {

        /** Value tag. */
        private final int tag;

        /** Number of values. */
        private final int n;

        /** Index of next value to be set (going backwards). */
        private int next;

        /** Values. */
        private final Value[] elements;

        /**
         * Constructs a <i>Values</i> from tag and size.
         * @param tag value tag
         * @param n closure size - should be <tt>&gt;= 0</tt>
         */
        private Values(final int tag, final int n) {
            assert n > 0 : "n should be > 0";
            this.tag = tag;
            this.n = n;
            this.next = n - 1;
            this.elements = new Value[n];
        } // end constructor(int, int)

    } // end inner-class 'Values'

    /**
     * A <i>Closure</i> is a mere data structure containg a list of values.
     */
    private static final class Closure {

        /** Closure index. */
        private final int index;

        /** Number of values in the closure. */
        private final int n;

        /** Values in the closure. */
        private final Value[] elements;

        /** Index of next value to be set (going backwards). */
        private int next;

        /**
         * Constructs a closure from index and size.
         * @param index closure index - should be <tt>&gt; 0</tt>
         * @param n closure size - should be <tt>&gt;= 0</tt>
         */
        private Closure(final int index, final int n) {
            assert index > 0 : "index should be > 0";
            assert n >= 0 : "n should be >= 0";
            this.index = index;
            this.n = n;
            this.elements = new Value[n];
            this.next = n - 1;
        } // end constructor(int, int)

    } // end inner-class 'Closure'

} // end class 'AbstractNativeRunner'
